
;系统加电最初1mb内存是bios分配的
;
; 0x00000-0x003ff  中断向量表
; 0x00400-0x004ff  bios 数据区
; 0x00500-0x07bff  自由内存区
; 0x07c00-0x07dff  引导程序加载区  512b
; 0x07e00-0x9ffff  自由内存区
; 0xa0000-0xbffff  显示内存区
; 0xc0000-0xfffff  中断处理程序




;nasm loader.asm -o loader.bin
;汇编语言源程序中若没有ORG伪指令，则程序执行时，指令代码被放到自由内存空间的CS:0处；若有ORG伪指令，
;编译器则把其后的指令代码放到ORG伪指令指定的偏移地址。两个ORG伪指令之间，除了指令代码，若有自由空间，则用0填充
;在汇编语言源程序的开始通常都用一条ORG伪指令来实现规定程序的起始地址。如果不用ORG规定则汇编得到的目标程序将从0000H开始
;org 不是机器指令是 汇编伪指令  ORG不再是绝对定位，只能在一个段里相对定位
org  0100h  
jmp	LABEL_START		; Start

;常量定义
baseOfStack		equ	0100h
baseOfKernelFile	equ	08000h	; KERNEL.BIN 被加载到的位置 ----  段地址
offsetOfKernelFile	equ	0h	; KERNEL.BIN 被加载到的位置 ---- 偏移地址
baseOfKernelFilePhyAddr	equ	baseOfKernelFile * 10h  ; 实模式下 段*16 +offset 即物理地址 
kernelEntryPointPhyAddr	equ	030400h	; 注意：1、必须与 MAKEFILE 中参数 -Ttext 的值相等!!
					;       2、这是个地址而非仅仅是个偏移，如果 -Ttext 的值为 0x400400，则它的值也应该是 0x400400。
baseOfLoader	        equ	 09000h	; LOADER.BIN 被加载到的位置 ----  段地址
offsetOfLoader	        equ	 0100h	; LOADER.BIN 被加载到的位置 ----  偏移地址
baseOfLoaderPhyAddr     equ	 baseOfLoader*10h ; LOADER.BIN 被加载到的位置 ---- 物理地址   实模式下 段*16 +offset 即物理地址



FATSz			equ	9       ; BPB_FATSz16 fat表占用扇区数量
rootDirSectors		equ	14      ; 根目录占用空间: rootDirSectors = ((BPB_RootEntCnt*32)+(BPB_BytsPerSec–1))/BPB_BytsPerSec
rootSector	        equ	19      ; Root  的第一个扇区号	= BPB_RsvdSecCnt + (BPB_NumFATs * FATSz)
sectorNoOfFAT1		equ	1       ; FAT1 的第一个扇区号	= BPB_RsvdSecCnt

; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2
; 文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo
deltaSectorNo		equ	17


;进入保护模式之前要保存到内存的一些信息 ,0x00500-0x07bff  自由内存区 
cyls  equ 05f0h ;设定启动区
leds  equ 05f1h
vmode equ 05f2h ;关于颜色数据的信息,颜色的位数
scrnx equ 05f4h ;分辨率x
scrny equ 05f6h ;分辨率y
vram  equ 05f8h ;图像缓存区的开始地址




; FAT12 磁盘的头
; ----------------------------------------------------------------------
BS_OEMName	DB 'winix1.0'	; OEM String, 必须 8 个字节
BPB_BytsPerSec	DW 512		; 每扇区字节数
BPB_SecPerClus	DB 1		; 每簇多少扇区
BPB_RsvdSecCnt	DW 1		; Boot 记录占用多少扇区
BPB_NumFATs	DB 2		; 共有多少 FAT 表
BPB_RootEntCnt	DW 224		; 根目录文件数最大值
BPB_TotSec16	DW 2880		; 逻辑扇区总数
BPB_Media	DB 0xF0		; 媒体描述符
BPB_FATSz16	DW 9		; 每FAT扇区数
BPB_SecPerTrk	DW 18		; 每磁道扇区数
BPB_NumHeads	DW 2		; 磁头数(面数)
BPB_HiddSec	DD 0		; 隐藏扇区数
BPB_TotSec32	DD 0		; 如果 wTotalSectorCount 是 0 由这个值记录扇区数

BS_DrvNum	DB 0		; 中断 13 的驱动器号
BS_Reserved1	DB 0		; 未使用
BS_BootSig	DB 29h		; 扩展引导标记 (29h)
BS_VolID	DD 0		; 卷序列号
BS_VolLab	DB 'winix  v1.0'; 卷标, 必须 11 个字节
BS_FileSysType	DB 'FAT12   '	; 文件系统类型, 必须 8个字节  
;------------------------------------------------------------------------

;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
;       DA_  : Descriptor Attribute
;       D    : 数据段
;       C    : 代码段
;       S    : 系统段
;       R    : 只读
;       RW   : 读写
;       A    : 已访问
;       其它 : 可按照字面意思理解
;----------------------------------------------------------------------------
DA_32		EQU	4000h	; 32 位段
DA_LIMIT_4K	EQU	8000h	; 段界限粒度为 4K 字节

DA_DPL0		EQU	  00h	; DPL = 0
DA_DPL1		EQU	  20h	; DPL = 1
DA_DPL2		EQU	  40h	; DPL = 2
DA_DPL3		EQU	  60h	; DPL = 3
;----------------------------------------------------------------------------
; 存储段描述符类型值说明
;----------------------------------------------------------------------------
DA_DR		EQU	90h	; 存在的只读数据段类型值
DA_DRW		EQU	92h	; 存在的可读写数据段属性值
DA_DRWA		EQU	93h	; 存在的已访问可读写数据段类型值
DA_C		EQU	98h	; 存在的只执行代码段属性值
DA_CR		EQU	9Ah	; 存在的可执行可读代码段属性值
DA_CCO		EQU	9Ch	; 存在的只执行一致代码段属性值
DA_CCOR		EQU	9Eh	; 存在的可执行可读一致代码段属性值
;----------------------------------------------------------------------------
; 系统段描述符类型值说明
;----------------------------------------------------------------------------
DA_LDT		EQU	  82h	; 局部描述符表段类型值
DA_TaskGate	EQU	  85h	; 任务门类型值
DA_386TSS	EQU	  89h	; 可用 386 任务状态段类型值
DA_386CGate	EQU	  8Ch	; 386 调用门类型值
DA_386IGate	EQU	  8Eh	; 386 中断门类型值
DA_386TGate	EQU	  8Fh	; 386 陷阱门类型值
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
;       SA_  : Selector Attribute

SA_RPL0		EQU	0	; ┓
SA_RPL1		EQU	1	; ┣ RPL
SA_RPL2		EQU	2	; ┃
SA_RPL3		EQU	3	; ┛

SA_TIG		EQU	0	; ┓TI
SA_TIL		EQU	4	; ┛
;----------------------------------------------------------------------------


;----------------------------------------------------------------------------
; 分页机制使用的常量说明
;----------------------------------------------------------------------------
PG_P		EQU	1	; 页存在属性位
PG_RWR		EQU	0	; R/W 属性位值, 读/执行
PG_RWW		EQU	2	; R/W 属性位值, 读/写/执行
PG_USS		EQU	0	; U/S 属性位值, 系统级
PG_USU		EQU	4	; U/S 属性位值, 用户级
;----------------------------------------------------------------------------

; 宏定义 ------------------------------------------------------------------------------------------------------

; 描述符
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
	dw	%2 & 0FFFFh				; 段界限 1				(2 字节)
	dw	%1 & 0FFFFh				; 段基址 1				(2 字节)
	db	(%1 >> 16) & 0FFh			; 段基址 2				(1 字节)
	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 属性 1 + 段界限 2 + 属性 2		(2 字节)
	db	(%1 >> 24) & 0FFh			; 段基址 3				(1 字节)
%endmacro ; 共 8 字节

; 门
; usage: Gate Selector, Offset, DCount, Attr
;        Selector:  dw
;        Offset:    dd
;        DCount:    db
;        Attr:      db
%macro Gate 4
	dw	(%2 & 0FFFFh)				; 偏移 1				(2 字节)
	dw	%1					; 选择子				(2 字节)
	dw	(%3 & 1Fh) | ((%4 << 8) & 0FF00h)	; 属性					(2 字节)
	dw	((%2 >> 16) & 0FFFFh)			; 偏移 2				(2 字节)
%endmacro ; 共 8 字节



; GDT
;                            段基址     段界限,          属性
LABEL_GDT:	    Descriptor 0,            0,      0                        ; 空描述符
LABEL_DESC_FLAT_C:  Descriptor 0,      0fffffh,      DA_CR|DA_32|DA_LIMIT_4K  ;0-4G
LABEL_DESC_FLAT_RW: Descriptor 0,      0fffffh,      DA_DRW|DA_32|DA_LIMIT_4K ;0-4G
LABEL_DESC_VIDEO:   Descriptor 0a0000h, 0ffffh,      DA_DRW|DA_DPL3           ; 显存首地址

GdtLen		equ	$ - LABEL_GDT
GdtPtr		dw	GdtLen - 1				; 段界限
		dd	baseOfLoaderPhyAddr + LABEL_GDT		; 基地址

; GDT 选择子
SelectorFlatC		equ	LABEL_DESC_FLAT_C	- LABEL_GDT
SelectorFlatRW		equ	LABEL_DESC_FLAT_RW	- LABEL_GDT
SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT + SA_RPL3


BaseOfStack	equ	0100h
PageDirBase	equ	100000h	; 页目录开始地址: 1M
PageTblBase	equ	101000h	; 页表开始地址:   1M + 4K


LABEL_START:			; <--- 从这里开始 *************
	mov	ax, cs          ;此时cs=baseOfLoader=09000h ,由 baseOfLoader:offsetOfLoader 转移过来
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, baseOfStack

	;mov	dh, 0			; "Loading  "
	;call	dispStr			; 显示字符串

	; 下面在 A 盘的根目录寻找 KERNEL.BIN
	mov	word [readSectorNo], rootSector	
	xor	ah, ah	; `.
	xor	dl, dl	;  | 软驱复位
	int	13h	; /
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
	cmp	word [rootSectors], 0	; `.
	jz	LABEL_NO_KERNELBIN		;  | 判断根目录区是不是已经读完,
	dec	word [rootSectors]	; /  读完表示没有找到 KERNEL.BIN
	mov	ax, baseOfKernelFile
	mov	es, ax			; es <- BaseOfKernelFile
	mov	bx, offsetOfKernelFile	; bx <- OffsetOfKernelFile
	mov	ax, [readSectorNo]		; ax <- Root Directory 中的某 Sector 号
	mov	cl, 1
	call	readSector

	mov	si, kernelFileName	; ds:si -> "KERNEL  BIN"
	mov	di, offsetOfKernelFile
	cld
	mov	dx, 10h
LABEL_SEARCH_FOR_KERNELBIN:
	cmp	dx, 0				  ; `.
	jz	LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR;  | 循环次数控制, 如果已经读完
	dec	dx				  ; /  了一个 Sector, 就跳到下一个
	mov	cx, 11
LABEL_CMP_FILENAME:
	cmp	cx, 0			; `.
	jz	LABEL_FILENAME_FOUND	;  | 循环次数控制, 如果比较了 11 个字符都
	dec	cx			; /  相等, 表示找到
	lodsb				; ds:si -> al
	cmp	al, byte [es:di]	; if al == es:di
	jz	LABEL_GO_ON
	jmp	LABEL_DIFFERENT
LABEL_GO_ON:
	inc	di
	jmp	LABEL_CMP_FILENAME	;	继续循环

LABEL_DIFFERENT:
	and	di, 0FFE0h		; else`. 让 di 是 20h 的倍数
	add	di, 20h			;      |
	mov	si, kernelFileName	;      | di += 20h  下一个目录条目
	jmp	LABEL_SEARCH_FOR_KERNELBIN;   /

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
	add	word [readSectorNo], 1
	jmp	LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_KERNELBIN:
	mov	dh, 2			; "No KERNEL."
	call	dispStr			; 显示字符串
%ifdef	_LOADER_DEBUG_
	mov	ax, 4c00h		; `.
	int	21h			; / 没有找到 KERNEL.BIN, 回到 DOS
%else
	jmp	$			; 没有找到 KERNEL.BIN, 死循环在这里
%endif

LABEL_FILENAME_FOUND:			; 找到 KERNEL.BIN 后便来到这里继续
	mov	ax, rootDirSectors
	and	di, 0FFF0h		; di -> 当前条目的开始

	push	eax
	mov	eax, [es : di + 01Ch]		; `.
	mov	dword [dwKernelSize], eax	; / 保存 KERNEL.BIN 文件大小
	pop	eax

	add	di, 01Ah		; di -> 首 Sector
	mov	cx, word [es:di]
	push	cx			; 保存此 Sector 在 FAT 中的序号
	add	cx, ax
	add	cx, deltaSectorNo	; cl <- KERNEL.BIN 的起始扇区号(0-based)
	mov	ax, baseOfKernelFile
	mov	es, ax			; es <- BaseOfKernelFile
	mov	bx, offsetOfKernelFile	; bx <- OffsetOfKernelFile
	mov	ax, cx			; ax <- Sector 号

LABEL_GOON_LOADING_FILE:
	;push	ax			; `.
	;push	bx			;  |
	;mov	ah, 0Eh			;  | 每读一个扇区就在 "Loading  " 后面
	;mov	al, '.'			;  | 打一个点, 形成这样的效果:
	;mov	bl, 0Fh			;  | Loading ......
	;int	10h			;  |
	;pop	bx			;  |
	;pop	ax			; /

	mov	cl, 1
	call	readSector
	pop	ax			; 取出此 Sector 在 FAT 中的序号
	call	getFATEntry
	cmp	ax, 0FFFh
	jz	LABEL_FILE_LOADED
	push	ax			; 保存 Sector 在 FAT 中的序号
	mov	dx, rootDirSectors
	add	ax, dx
	add	ax, deltaSectorNo
	add	bx, [BPB_BytsPerSec]
	jmp	LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:
	call	killMotor		; 关闭软驱马达

	

	;在进入保护模式之前需要保存一些信息到内存
        mov     al,13h   ;vga 显卡 320*200*8位彩色
	mov     ah,00h
	int     10h      ;显示模式设置为 vga
       
        ;保存到绝对地址 05f0h ,0x00500-0x07bff  自由内存区
        push    ds
	mov     ax,0
	mov     ds,ax
        mov     byte  [vmode],8      ;记录显示模式
	mov     word  [scrnx],320    
        mov     word  [scrny],200
	mov     dword [vram] ,0a0000h
	pop     ds
        
	;利用bios 获取键盘上led 指示灯的状态
	mov      ah,02h
	int      16h
	mov      [leds],al


	; 下面准备跳入保护模式

	; 加载 GDTR
	lgdt	[GdtPtr]

	; 关中断
	cli

	; 打开地址线A20
	in	al, 92h
	or	al, 00000010b
	out	92h, al

	; 准备切换到保护模式
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax

	; 真正进入保护模式
	jmp	dword SelectorFlatC:(baseOfLoaderPhyAddr+LABEL_PM_START)



;变量=========================================================================

rootSectors	dw	rootDirSectors	; Root Directory 占用的扇区数
readSectorNo		dw	0		        ; 要读取的扇区号
bOdd			db	0		        ; 奇数还是偶数
dwKernelSize		dd	0		        ; KERNEL.BIN 文件大小

kernelFileName		db	"KERNEL  BIN", 0	; KERNEL.BIN 之文件名

messageLength		equ	11
loadMessage:		db	"loadKernel "
message1		db	"kernelReady"
message2		db	"No KERNEL  "
;============================================================================

;----------------------------------------------------------------------------
; 函数名: DispStr
;----------------------------------------------------------------------------
; 作用:
;	显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
dispStr:
	mov	ax, messageLength
	mul	dh
	add	ax, loadMessage
	mov	bp, ax			; ┓
	mov	ax, ds			; ┣ ES:BP = 串地址
	mov	es, ax			; ┛
	mov	cx, messageLength	; CX = 串长度
	mov	ax, 01301h		; AH = 13,  AL = 01h
	mov	bx, 0007h		; 页号为0(BH = 0) 黑底白字(BL = 07h)
	mov	dl, 0
	add	dh, 3			; 从第 3 行往下显示
	int	10h			; int 10h
	ret
;----------------------------------------------------------------------------
; 函数名: ReadSector
;----------------------------------------------------------------------------
; 作用:
;	从序号(Directory Entry 中的 Sector 号)为 ax 的的 Sector 开始, 将 cl 个 Sector 读入 es:bx 中
readSector:
	; -----------------------------------------------------------------------
	; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
	; -----------------------------------------------------------------------
	; 设扇区号为 x
	;                           ┌ 柱面号 = y >> 1
	;       x           ┌ 商 y ┤
	; -------------- => ┤      └ 磁头号 = y & 1
	;  每磁道扇区数     │
	;                   └ 余 z => 起始扇区号 = z + 1
	push	bp
	mov	bp, sp
	sub	esp, 2			; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]

	mov	byte [bp-2], cl
	push	bx			; 保存 bx
	mov	bl, [BPB_SecPerTrk]	; bl: 除数
	div	bl			; y 在 al 中, z 在 ah 中
	inc	ah			; z ++
	mov	cl, ah			; cl <- 起始扇区号
	mov	dh, al			; dh <- y
	shr	al, 1			; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
	mov	ch, al			; ch <- 柱面号
	and	dh, 1			; dh & 1 = 磁头号
	pop	bx			; 恢复 bx
	; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
	mov	dl, [BS_DrvNum]		; 驱动器号 (0 表示 A 盘)
goOnReading:
	mov	ah, 2			; 读
	mov	al, byte [bp-2]		; 读 al 个扇区
	int	13h
	jc	goOnReading		; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

	add	esp, 2
	pop	bp

	ret

;----------------------------------------------------------------------------
; 函数名: GetFATEntry
;----------------------------------------------------------------------------
; 作用:
;	找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
;	需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx
getFATEntry:
	push	es
	push	bx
	push	ax
	mov	ax, baseOfKernelFile	; ┓
	sub	ax, 0100h		; ┣ 在 BaseOfKernelFile 后面留出 4K 空间用于存放 FAT
	mov	es, ax			; ┛
	pop	ax
	mov	byte [bOdd], 0
	mov	bx, 3
	mul	bx			; dx:ax = ax * 3
	mov	bx, 2
	div	bx			; dx:ax / 2  ==>  ax <- 商, dx <- 余数
	cmp	dx, 0
	jz	LABEL_EVEN
	mov	byte [bOdd], 1
LABEL_EVEN:;偶数
	xor	dx, dx			; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
	mov	bx, [BPB_BytsPerSec]
	div	bx			; dx:ax / BPB_BytsPerSec  ==>	ax <- 商   (FATEntry 所在的扇区相对于 FAT 来说的扇区号)
					;				dx <- 余数 (FATEntry 在扇区内的偏移)。
	push	dx
	mov	bx, 0			; bx <- 0	于是, es:bx = (BaseOfKernelFile - 100):00 = (BaseOfKernelFile - 100) * 10h
	add	ax, sectorNoOfFAT1	; 此句执行之后的 ax 就是 FATEntry 所在的扇区号
	mov	cl, 2
	call	readSector		; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区
	pop	dx
	add	bx, dx
	mov	ax, [es:bx]
	cmp	byte [bOdd], 1
	jnz	LABEL_EVEN_2
	shr	ax, 4
LABEL_EVEN_2:
	and	ax, 0FFFh

LABEL_GET_FAT_ENRY_OK:

	pop	bx
	pop	es
	ret
;----------------------------------------------------------------------------


;----------------------------------------------------------------------------
; 函数名: KillMotor
;----------------------------------------------------------------------------
; 作用:
;	关闭软驱马达
killMotor:
	push	dx
	mov	dx, 03F2h
	mov	al, 0
	out	dx, al
	pop	dx
	ret
;----------------------------------------------------------------------------

;----------------------------------------------------------------------------
; 函数名: DispStrRealMode
;----------------------------------------------------------------------------
; 运行环境:
;	实模式（保护模式下显示字符串由函数 DispStr 完成）
; 作用:
;	显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
dispStrRealMode:
	mov	ax, messageLength
	mul	dh
	add	ax, loadMessage
	mov	bp, ax			; ┓
	mov	ax, ds			; ┣ ES:BP = 串地址
	mov	es, ax			; ┛
	mov	cx, messageLength	; CX = 串长度
	mov	ax, 01301h		; AH = 13,  AL = 01h
	mov	bx, 0007h		; 页号为0(BH = 0) 黑底白字(BL = 07h)
	mov	dl, 0
	add	dh, 3			; 从第 3 行往下显示
	int	10h			; int 10h
	ret


;******************************************************************\\\******************×
;*******************************************************************\\\*****************×
;*******************************************************************///*****************×           
;******************************************************************///*****************×*
; 以下是进入保护模式模式后的代码
[SECTION .s32]
ALIGN	32
[BITS	32]
LABEL_PM_START:

	mov	ax, SelectorVideo
	mov	gs, ax

	mov	ax, SelectorFlatRW
	mov	ds, ax
	mov	es, ax
	mov	fs, ax
	mov	ss, ax
	mov	esp,TopOfStack

	call	initKernel ; 初始化内核
	; kernelEntryPointPhyAddr=030400h ld -s -Ttext 030400 -o kernel.bin kernal.o main.o
        jmp	SelectorFlatC:kernelEntryPointPhyAddr	; 正式进入内核 *
	
;***************************************************************************************×



; InitKernel ---------------------------------------------------------------------------------
; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置
; 因为kernel.bin 为elf 文件,该文件包含有文件头不是单纯的 纯指令文件 所以需要解析
; linux 下 objcopy 好像可以把elf 文件生成单纯的bin 文件,但是还没有实验过 20130319 by xuejianxin
; add by xuejianxin  kernel.bin 加载到物理内存的地址为 080000h 
; 遍历每一个 Program Header，根据 Program Header 中的信息来确定把什么放进内存，放到什么位置，以及放多少。
; 放到保护模式下 [0选择子:p_vaddr] 中去
; --------------------------------------------------------------------------------------------
initKernel:
        ;清零esi
        xor   esi, esi
	;第124页 elf 程序头部条目个数 偏移位置 44=2ch
        mov   cx, word [baseOfKernelFilePhyAddr+2Ch]; ecx <- pELFHdr->e_phnum
        movzx ecx, cx                               ;
	;第123页 程序头部偏移 28 =1ch
        mov   esi, [baseOfKernelFilePhyAddr + 1Ch]  ;esi <- pELFHdr->e_phoff
        add   esi, baseOfKernelFilePhyAddr          ;esi<-OffsetOfKernel+pELFHdr->e_phoff
.Begin:
        mov   eax, [esi + 0]
        cmp   eax, 0                      ; PT_NULL
        jz    .NoAction
	;第 125页 段在文件中长度 ,偏移位置 16=10h
        push  dword [esi + 010h]    ;size 
	;第125 页 段位置相对于elf 文件开始出偏移位置,  4=04h
        mov   eax,  [esi + 04h]            ; |
        add   eax, baseOfKernelFilePhyAddr; | memcpy((void*)(pPHdr->p_vaddr),uchCode + pPHdr->p_offset,pPHdr->p_filesz;
        ; 源地址 [0选择子:baseOfKernelFilePhyAddr+段在文件中偏移]
	push  eax		       
        ;第125页段在内存中的地址[p_vaddr] 偏移为8=08h   拷贝的目的地址  [0选择子:p_vaddr],其中 p_vaddr 为030400h 在连接程序时指定
	push  dword [esi + 08h]             
        call  memCpy                      ;
        add   esp, 12                     ; ;push 3次 共push 12 字节,依次是: 段长度,源地址,目的地址
.NoAction:
        add   esi, 020h                   ; esi += pELFHdr->e_phentsize
        dec   ecx
        jnz   .Begin

        ret
; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


; ------------------------------------------------------------------------
; 内存拷贝，仿 memcpy
; ------------------------------------------------------------------------
; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------
memCpy:
	push	ebp
	mov	ebp, esp

	push	esi
	push	edi
	push	ecx
       ;ebp+4 是返回地址ip 用于ret 见80x86 第126页
	mov	edi, [ebp + 8]	; Destination
	mov	esi, [ebp + 12]	; Source
	mov	ecx, [ebp + 16]	; Counter
.1:
	cmp	ecx, 0		; 判断计数器
	jz	.2		; 计数器为零时跳出

	mov	al, [ds:esi]		; ┓
	inc	esi			; ┃
					; ┣ 逐字节移动
	mov	byte [es:edi], al	; ┃
	inc	edi			; ┛

	dec	ecx		; 计数器减一
	jmp	.1		; 循环
.2:
	mov	eax, [ebp + 8]	; 返回值

	pop	ecx
	pop	edi
	pop	esi
	mov	esp, ebp
	pop	ebp

	ret			; 函数结束，返回
; MemCpy 结束-------------------------------------------------------------



; SECTION .data1 之开始 ---------------------------------------------------------------------------------------------
[SECTION .data1]
ALIGN	32
LABEL_DATA:
; 堆栈就在数据段的末尾
StackSpace:	times	1024	db	0
TopOfStack	equ	baseOfLoaderPhyAddr + $	; 栈顶
; SECTION .data1 之结束

